/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.rocketmq.store.timer;

/**
 * Represents a slot of timing wheel. Format:
 * ┌────────────┬───────────┬───────────┬───────────┬───────────┐
 * │delayed time│ first pos │ last pos  │    num    │   magic   │
 * ├────────────┼───────────┼───────────┼───────────┼───────────┤
 * │   8bytes   │   8bytes  │  8bytes   │   4bytes  │   4bytes  │
 * └────────────┴───────────┴───────────┴───────────┴───────────┘
 */

// 时间轮槽位，每个槽位在Timerwheel文件中占固定32字节
public class Slot {
    public static final short SIZE = 32;  // 槽位大小
    /**
     * timeMs 这个属性我详细说下，不太好理解
     * 他本质是延迟消息时间。
     * 比如：假如现在时间轮有4个槽
     * 一个定时消息put过来，他的延迟绝对时间戳假如是A(当然这个时间A是会被对齐一下的)
     * 通过计算得到他属于0 - 1槽位
     * 那么这个时间戳A就会被赋值给这个槽位的timeMs
     *
     * 假如又来了一个消息呢，时间戳是B，计算得出也是在0-1这个槽位，那么又会将新的时间戳B赋值给这个槽位的timeMs
     *
     * 所以槽位的timeMs是随着后面的消息而变化的
     * 且每次变化都是n个时间轮范围
     * 如4个时间轮总时间范围是4天，那么B - A 肯定相差 n * 4。这很好理解
     * 因为定时消息的时间戳是会被重新计算的(对齐)，并不是消费端指定多少就是多少。
     *
     * 其次不用担心线程安全问题，因为put TimerLog消息以及更新时间轮都是一个固定的线程在处理
     * @see TimerMessageStore#doEnqueue(long, int, long, org.apache.rocketmq.common.message.MessageExt)
     *
     * 但是请注意：
     * 槽位信息在timerwheel中存储时 timeMs并不是直接存储的对齐时间，而是存储的对接槽位索引（也就是实际是timeMs / 槽位时间间隔）
     * 当获取槽位信息时，在乘以槽位时间间隔 放入timeMs字段。
     * @see TimerWheel#putSlot(long, long, long, int, int)
     *
     * 举例：
     * 时间戳A(对齐后的，因为延迟时间首先就是要进行对齐)，除以槽位时间单元(假如时1天)
     *
     * 那么A / 3600 * 1000(1天转为毫秒) = 假如是100.
     *
     * 而时间轮的槽位数是4，因此A就应该在 100 % 4 = 0。就应该是第一个槽位。0是相对槽位说，100我称它为绝对槽位数。
     *
     * 但是请注意：
     * rocketmq的时间轮和netty的有区别。
     * 1、netty的时间轮没有过期限制，但rocketma的有
     *
     * 如：有4个槽，每个槽的时间间隔1天，对于rocketmq而言，他就最多只能保存4天的东西
     * 因为第五天的消息，会将第一个槽的timeMs给覆盖掉，导致之前消息校验timeMs失败
     * @see TimerWheel#getSlot(long) 的if判断
     *
     * 何为时间对齐，我举个例子
     * 现在一个槽位的时间间隔是5
     *
     * 那么也就是说你的4是落在这个槽位 1也是落在这个槽位，那么4就会被对齐为1
     *
     * 时间戳 / 时间轮总长度 * 槽位间隔时间
     *
     * */
    public final long timeMs; //delayed time  槽位绝对延迟时间
    public final long firstPos; // 此槽位第一条消息的结束偏移量
    public final long lastPos; // 当前槽位最后一条消息的结束偏移量
    public final int num; // 槽位中消息数量
    public final int magic; //no use now, just keep it

    public Slot(long timeMs, long firstPos, long lastPos) {
        this.timeMs = timeMs;
        this.firstPos = firstPos;
        this.lastPos = lastPos;
        this.num = 0;
        this.magic = 0;
    }

    public Slot(long timeMs, long firstPos, long lastPos, int num, int magic) {
        this.timeMs = timeMs;
        this.firstPos = firstPos;
        this.lastPos = lastPos;
        this.num = num;
        this.magic = magic;
    }
}
